<?php
/**
 * Created by PhpStorm.
 * User: Inhere
 * Date: 2015/2/11
 * Use : ...
 * File: AbstractWebController.php
 */

namespace ulue\core\controller;

use Ioc;
use Ulue,
    Debug;

use ulue\core\router\AbstractRouter;
use ulue\libs\helpers\PublicHelper;
use ulue\core\view\AbstractView;
use ulue\web\View;

abstract class AbstractWebController extends AbstractController
{
    public $layout;         //布局文件
    public $theme;          //主题
    public $layoutSuffix = '.php';   //布局文件后缀,默认与视图文件的相同
    public $viewSuffix = '.html';     //视图文件后缀

    public $viewFile;       //将要渲染的视图文件
    //public $autoRender  = false;    //自动渲染
    public $breadcrumbs    = [];    //面包屑导航

    /**
     * @var \ulue\core\response\AbstractResponse
     */
    protected $response;

    /**
     * @var $view \ulue\core\view\AbstractView
     */
    protected $view       = null;     //实例化的视图类对象

    /**
     * @var $_view \ulue\core\view\AbstractView
     */
    // protected $_view       = null;     //实例化的视图类对象

    public function __construct(array $options=[])
    {
        Ulue::loadAttrs($this, $options);

    }

    /**
     * 覆盖 init() 时,请记得先 调用 parent::init(); 以防止出现错误
     * @return void
     */
    protected function init()
    {
        parent::init();

        // 若控制器中没有设置 layout theme .. 等属性,则读取 Module 中配置的默认值
        $m = Ulue::$app->module;

        !$this->layout && $this->layout = $m->layout;
        !$this->theme  && $this->theme  = $m->theme;

        unset($m);
    }

################################ run action method ################################

    /**
     * 存入的是当前控制器类中的方法名
     * return array('index','add','delete','edit');
     */
    protected function requireBeforeExecute()
    {
        return [];
    }

    /**
     * 1.每次控制器文件运行run()时会自动先执行 beforeExecute()，即是执行当前控制器下的所有方法前都会先执行 beforeRun()
     * 2.若仅需在某一些方法执行前需要自动执行 beforeExecute(),请在 requireBeforeExecute()中放入需要先执行 beforeExecute()的 方法列表
     * 可用于事先的 验证（用户验证） 及 获取公共数据（导航栏）
     * @return bool
     */
    protected function beforeExecute()
    {
        return true;
    }

    /**
     * 在执行前做一些检查准备工作
     * @param $actionName
     * @throws \DRuntimeException
     */
    protected function prepareExecute( &$actionName )
    {
        $beforeRuns = (array) $this->requireBeforeExecute();

        // 仅在某一些方法执行前需要先自动执行 beforeExecute()
        if ( ! $beforeRuns ) {

            if ( in_array( $actionName, $beforeRuns) && !$this->beforeExecute() ) {
                throw new \DRuntimeException(get_class()."->beforeExecute() returned the false！");
            }

        // 默认：当前控制器下的所有web请求方法执行前 都需要 先自动执行 beforeExecute()
        } else if ( !$this->beforeExecute() ) {
            throw new \DRuntimeException( get_class()."->beforeExecute() returned the false！");
        }
    }

    /**
     * 运行控制器下的方法
     * 1. 操作没有绑定到类，常规模式 self::_executeInternalAction
     * e.g. request: index.php/site/about
     *  class SiteController -- SiteController->aboutAction() --- load view -- return view data
     * 2. 控制器中设置操作绑定到类 ... self::_executeBindClassAction()
     * e.g. request: index.php/site/about
     *  class SiteController --(action bound to class)--class site/AboutAction
     *  -- AboutAction->execute()- load view -- return view data
     * @throws \DRuntimeException
     * @return mixed|void
     */
    public function execute()
    {
        $actionName = $this->action->name;

        Debug::trace("i 将运行路由动作方法 \$actionName :  $actionName");

        $this->prepareExecute($actionName);

        // todo action method 是否使用 后缀 index -> indexAction
        $theActionMethod = $this->action->methodName;
        // $theActionMethod = $actionName;

        /**
         * @see \ulue\core\router\Router::$params
         * @var $params array 请求参数部分
         */
        $params = Ulue::$app->router->getParams();

        # 1. 控制器存在 $actionName 方法 ，@example public function index()
        if ( method_exists( $this, $theActionMethod ) ) {
            $output = $this->_executeInternalAction($actionName, $theActionMethod, $params);

        # 2. 设置操作绑定到action类
        } else if ( $this->checkActionIsBindToClass($actionName) ) {
            $output = $this->_executeBindClassAction($actionName, $params);

        # 3. 开启了调试
        } else if (APP_DEBUG) {
            throw new \DRuntimeException(get_class($this)."->{$theActionMethod}() 方法不存在！");

        # 4. 跳至 错误页面
        } else {
            $output = $this->_theErrorAction();
        }

        Debug::trace(sprintf(" :) 控制器方法 %s->{$theActionMethod}() 执行完毕！",get_class()));

        $this->afterExecute($output);

        return $this->setOutput($output)->getOutput();
    }

    protected function afterExecute( & $output )
    {
        // ...
    }

    /**
     * 执行控制器内的 路由请求动作
     * @param $actionName
     * @param $theActionMethod
     * @param array $params
     * @throws \DRuntimeException
     * @return mixed
     */
    private function _executeInternalAction($actionName, $theActionMethod, array $params=[])
    {
        $ucfActionName = ucfirst($actionName);

        // 有 before{$ucfActionName} , 先执行 $beforeAction()方法
        // @example public function beforeIndex()
        if ( method_exists( $this, $beforeAction = 'before'.$ucfActionName ) ) {
            if ( !$this->$beforeAction() ) {
                throw new \DRuntimeException( get_class() . "::before{$ucfActionName}() Returned to the false!");
            }
        }

        // 执行请求的方法
        if( $params = $this->getMethodParameters($this, $theActionMethod, $params) ) {
            $output = call_user_func_array( [$this, $theActionMethod], (array) $params);
        } else {
            $output = $this->$theActionMethod();
        }

         /**有 after{$ucfActionName} @example public function afterIndex()*/
        if ( method_exists( $this, $afterAction = 'after'.$ucfActionName) ) {
            $this->$afterAction( $output);
        }

        return $output;
    }

    /**
     * 执行动作绑定到 action类的 execute() 方法
     * @param string $actionName
     * @param array  $params
     * @throws \DRuntimeException
     * @return string
     */
    private function _executeBindClassAction($actionName, array $params=[])
    {
        if ($actionClassId = $this->getActionBindClassId() ) {
            $actionClassname = PublicHelper::getClassname($actionClassId);
            $filePath        = Ulue::resolveNamespace($actionClassId).'.php';
        } else {
            $actionClassname = ucfirst($actionName).ucfirst( Ulue::$app->suffix['actionClass'] );
            $actionClassId   = PublicHelper::getNamespace($this->id) . "\\{$this->name}\\{$actionClassname}";
            $filePath        = $this->getPath()."/{$this->name}/{$actionClassname}.php";
        }

        $this->checkIsCorrectClass($filePath, $actionClassId, __NAMESPACE__.'\AbstractActionController' , '绑定动作');

        $options         = [
            'parent'        => $this,
            'path'          => dirname($filePath),
            'route'         => $this->route.'/'.$actionName,
            'id'            => $actionClassId,
            'name'          => $actionName,
            'className'     => $actionClassname,
        ];

        /**
         * @var $actionObject \ulue\core\controller\AbstractActionController
         */
        $actionObject  = new $actionClassId( $options);

        if ( !$actionObject->beforeExecute() ) {
            throw new \DRuntimeException("Bound Class's Action ".get_class($actionObject) .
             "::beforeExecute() Returned to the false!");
        }

        if (!method_exists($actionObject, 'execute')) {
            throw new \DRuntimeException("Please define method 'execute' at $actionClassId.");
        }

        if ($params = $this->getMethodParameters($actionObject, 'execute', $params) ) {
            $output = call_user_func_array( [$actionObject, 'execute'], (array) $params);
        } else {
            $output = $actionObject->execute();
        }

        $this->afterExecute($output);

        unset($options, $actionObject);

        return $output;
    }

    protected function _theErrorAction()
    {
        return $this->render(SYS_LIBS.'/debug/views/404.html');
    }


    /**
     * 重定向跳转
     * 'login' 'user/login' 'admin/user/login' '/index/index'
     * @param $url
     * @param int $time
     * @param null $msg
     * @return null
     */
    public function redirect($url, $time = 0, $msg = null)
    {
        if (!is_scalar($url)) {
            return false;
        }

        $url = rtrim($url,'/ '); //去掉后面多余的'/'

        // 是一个url地址
        if ( ! StringHelper::regexVerify($url,'url') ) {
            if ( strpos($url,'/') !== false ) {

                # 首字符 == '/' 指向根目录
                if (substr($url, 0,1) == '/')
                {
                    $url = Ioc::getShared('app.uri')->get('host.path') . $url;
                } else {
                    $routeArr = explode('/',$url,2);

                    //第一段是模块 'admin/user' 'admin/user/login'-->'admin' 在模块列表 Url::handleModule() 中
                    if ( Module::isModule($routeArr[0]) )
                    {
                        $url = Uri::to($url);
                    //第一段不是模块，即是控制器 'user/login',添上当前模块名
                    } else {
                        // $module     =   MODULE;
                        $module     =   Ulue::$app->getModule()->name;
                        $module     =   ($module == Ulue::$app->defaultModule)? '': $module.'/';
                        $url        =   Uri::to($module.$url);
                    }
                }

            #仅一段参数，默认定向到当前控制器下面的方法
            } else {
                $url = \Ioc::get('app.uri')->get('controller.path') . $url;
            }
        }

        if (!headers_sent()) {
            $time == 0 ? header("Location:" . $url) : header("refresh:{$time};url={$url}");

            if (!is_null($msg)) {
                echo('<h3>'.$msg.'</h3>');
            }
        } else {
            if (!is_null($msg)) {
                echo('<h3>'.$msg.'</h3>');
            }

            $js = "setTimeout(function(){window.location.href='$url';},$time*1000);";
            echo Html::script($js);
        }

        Ulue::quit();

        return null;
    }

################################ view control method ################################

    /**
     * 分配变量到视图，也可直接在render()时，传入 data=[]
     * @param $name
     * @param null $value
     */
    public function assign($name,$value=null)
    {
        $this->getView()->assign($name,$value);
    }

    /**
     * @return array
     */
    public function getViewVars()
    {
        return $this->getView()->getVars();
    }

    /**
     * 设置/获取布局方式
     * @param  integer $value [description]
     * @return int|null
     */
    public function layoutType($value=null)
    {
        return $this->getView()->layoutType($value);
    }

    /**
     * 渲染视图，会自动加载布局文件，显示加载视图文件并解析变量
     * @param string $view
     * @param array $data
     * @return bool|mixed|string
     */
    public function renderAll( $view='', array $data=[])
    {
        Debug::trace("i 渲染视图,会自动加载布局文件，显示加载视图文件并解析变量");

        return $this->getView()->renderAll( $view, $data);
    }
    public function resolveAll($view='', array $data=[])
    {
        return $this->renderAll( $view, $data);
    }

    /**
     * 局部渲染，显示输出视图文件内容到页面,没有布局，会解析变量
     * @param string $view
     * @param array $data
     * @return bool|mixed|string
     */
    public function render( $view='', array $data=[])
    {
        return $this->getView()->render($view, $data );
    }
    public function resolve($view='', array $data=[])
    {
        return $this->render( $view, $data);
    }
    public function renderPartial( $view='', array $data=[])
    {
        return $this->render( $view, $data);
    }

    /**
     * 简单的输出内容没有布局，不会解析变量，用于直接展示内容或静态的页面
     * $content 1.默认直接输出内容
     * $content 2.为文件名时候也可，必须带后缀ex: index.html news/index.html
     * @param string $content
     * @param int $return
     * @param array $param
     * @internal param int $type
     * @return string
     */
    public function showContent($content='', $return=1, $param=[] )
    {
        return $this->getView()->showContent( $content , $return ,$param );
    }


################################## attribute ##################################

    public function setView(AbstractView $view)
    {
        $this->view = $view;

        return $this;
    }

    /**
     * @return View object
     */
    public function getView()
    {
        if (!$this->view) {
            $this->activeViewMgr();
        }

        return $this->view;
    }

    public function activeViewMgr()
    {
        $options = [
               'layout'        => $this->layout,
               'layoutSuffix'  => $this->layoutSuffix,
               'theme'         => $this->theme,
               'viewSuffix'    => $this->viewSuffix,
           ];

        $this->view = Ioc::get('view', [ $options ] );
    }

}// end class AbstractWebController